unit UltimateSheduler_Defines;

interface

uses
 Types,
 Classes,
 SysUtils,
 Forms,
 ExtCtrls,
 SyncObjs,
 ShellAPI,
 gnugettext,
 XMLSerializer,
 LogFile;

type

 anAction = class(TCollectionItem)
  private
   FEnabled: Boolean;
   FActType: Integer; //one of act*
   FDest: String;
   FParam: String;
   FFlag: Boolean;
   FExisting: Integer; //one of actex*
  public
   procedure Assign(Source: TPersistent); override;
   procedure Clear;
  published
   property Enabled: Boolean read FEnabled write FEnabled;
   property ActType: Integer read FActType write FActType;
   property Destination: String read FDest write FDest;
   property Parameter: String read FParam write FParam;
   property Flag: Boolean read FFlag write FFlag;
   property Existing: Integer read FExisting write FExisting;
 end;

 TActions = class(TCollection)
  private
   function GetItem(Index: Integer): anAction;
   procedure SetItem(Index: Integer; const Value: anAction);
  public
   function Add: anAction;
   function Insert(Index: Integer): anAction;
   property Items[Index: Integer]: anAction read GetItem write SetItem; default;
 end;

 ActionGroup = class(TCollectionItem)
  private
   FTitle: String;
   FActions: TActions;
  public
   constructor Create(Collection: TCollection); override;
   destructor Destroy; override;
   procedure Assign(Source: TPersistent); override;
  published
   property Title: String read FTitle write FTitle;
   property Actions: TActions read FActions write FActions;
 end;

 TActionGroups = class(TCollection)
  private
   function GetItem(Index: Integer): ActionGroup;
   procedure SetItem(Index: Integer; const Value: ActionGroup);
  public
   function Add: ActionGroup;
   function Insert(Index: Integer): ActionGroup;
   property Items[Index: Integer]: ActionGroup read GetItem write SetItem; default;
   //
   function IndexOfTitle(const ATitle: String): Integer;
   function ListOfTitles: String;
 end;

 Task = class(TCollectionItem)
  private
   FEnabled: Boolean;
   FTitle: String;
   FLaunchType: Integer;
   FLaunchKind: Integer;
   FLaunchData: String;
   FRuleTitle: String;
   FActionsTitle: String;
   FLastLaunch: TDateTime;
   FNextLaunch: TDateTime;
  public
   constructor Create(Collection: TCollection); override;
   procedure Assign(Source: TPersistent); override;
   //
   procedure SetupNextLaunch;
  published
   property Enabled: Boolean read FEnabled write FEnabled;
   property Title: String read FTitle write FTitle;
   property LaunchType: Integer read FLaunchType write FLaunchType;
   property LaunchKind: Integer read FLaunchKind write FLaunchKind;
   property LaunchData: String read FLaunchData write FLaunchData;
   property RuleTitle: String read FRuleTitle write FRuleTitle;
   property ActionsTitle: String read FActionsTitle write FActionsTitle;
   property LastLaunch: TDateTime read FLastLaunch write FLastLaunch;
   property NextLaunch: TDateTime read FNextLaunch write FNextLaunch;
 end;

 USTasks = class(TCollection)
  private
   function GetItem(Index: Integer): Task;
   procedure SetItem(Index: Integer; const Value: Task);
  public
   function Add: Task;
   function Insert(Index: Integer): Task;
   property Items[Index: Integer]: Task read GetItem write SetItem; default;
   //
   function IndexOfTitle(const ATitle: String): Integer;
   function ListOfTitles: String;
   procedure SetupNextLaunchTime;
   function CheckLaunchTime: String;
   function CheckLaunchEvent(const Event: String): String;
 end;

 Condition = class(TCollectionItem)
  private
   FCondIndex: Char; //A..Z
   FCondType: Integer; //one of cnd*
   FParameter: String;
   FExternal: Boolean;
  public
   constructor Create(Collection: TCollection); override;
   procedure Assign(Source: TPersistent); override;
   procedure Clear;
   property ExternalFlag: Boolean read FExternal write FExternal;
  published
   property CondIndex: Char read FCondIndex write FCondIndex;
   property CondType: Integer read FCondType write FCondType;
   property Parameter: String read FParameter write FParameter;
 end;

 TConditions = class(TCollection)
  private
   function GetItem(Index: Integer): Condition;
   procedure SetItem(Index: Integer; const Value: Condition);
  public
   function Add: Condition;
   function Insert(Index: Integer): Condition;
   property Items[Index: Integer]: Condition read GetItem write SetItem; default;
   //
   function GetUsedIndexes: String;
 end;

 TExtCondFunc = function(const ACondition: Condition): Boolean of object;

 Rule = class(TCollectionItem)
  private
   FTitle: String;
   FFormula: String;
   FConditions: TConditions;
   FFunction: TExtCondFunc;
  public
   constructor Create(Collection: TCollection); override;
   destructor Destroy; override;
   procedure Assign(Source: TPersistent); override;
   //
   function Matched(const Info: String): Boolean;
   property ExtFunction: TExtCondFunc read FFunction write FFunction;
  published
   property Title: String read FTitle write FTitle;
   property Formula: String read FFormula write FFormula;
   property Conditions: TConditions read FConditions write FConditions;
 end;

 TRules = class(TCollection)
  private
   function GetItem(Index: Integer): Rule;
   procedure SetItem(Index: Integer; const Value: Rule);
  public
   function Add(AFunction: TExtCondFunc): Rule;
   function Insert(Index: Integer): Rule;
   property Items[Index: Integer]: Rule read GetItem write SetItem; default;
   //
   function IndexOfTitle(const ATitle: String): Integer;
   function ListOfTitles: String;
 end;

 ERuleException = class (Exception);
 
 Settings = class(TPersistent)
  private
   FTasks: USTasks;
   FRules: TRules;
   FActionGroups: TActionGroups;
   FLogEnabled: Boolean;
   FLogDM: Boolean;
   FLogSave: Integer;
   FLogSize: Cardinal;
   FSilent: Boolean;
  public
   constructor Create;
   destructor Destroy; override;
   procedure Assign(Source: TPersistent); override;
  published
   property LogEnabled: Boolean read FLogEnabled write FLogEnabled default false;
   property LogDM: Boolean read FLogDM write FLogDM default false;
   property LogSave: Integer read FLogSave write FLogSave default cTimeExit;
   property LogSize: Cardinal read FLogSize write FLogSize default 0;
   property Silent: Boolean read FSilent write FSilent default true;
   property Tasks: USTasks read FTasks write FTasks;
   property Rules: TRules read FRules write FRules;
   property ActionGroups: TActionGroups read FActionGroups write FActionGroups;
 end;

 SingleTask = class(TPersistent)
  private
   FTask: Task;
  public
   constructor Create;
   destructor Destroy; override;
   procedure Assign(Source: TPersistent); override;
  published
   property aTask: Task read FTask write FTask;
 end;

 SingleRule = class(TPersistent)
  private
   FRule: Rule;
  public
   constructor Create;
   destructor Destroy; override;
   procedure Assign(Source: TPersistent); override;
  published
   property aRule: Rule read FRule write FRule;
 end;

 SingleActionGroup = class(TPersistent)
  private
   FActionGroup: ActionGroup;
  public
   constructor Create;
   destructor Destroy; override;
   procedure Assign(Source: TPersistent); override;
  published
   property anActionGroup: ActionGroup read FActionGroup write FActionGroup;
 end;

 Process = class(TCollectionItem)
  private
   FTaskID: Integer;
   FRuleID: Integer;
   FActionGroupID: Integer;
   FActionID: Integer;
   //FDone: Integer;
   FInfo: String;
   FSEI: TShellExecuteInfo;
  public
   constructor Create(Collection: TCollection); override;
   procedure Assign(Source: TPersistent); override;
   procedure SEI_Clear;
  published
   property TaskID: Integer read FTaskID write FTaskID;
   property RuleID: Integer read FRuleID write FRuleID;
   property ActionGroupID: Integer read FActionGroupID write FActionGroupID;
   property ActionID: Integer read FActionID write FActionID;
   //property Done: Integer read FDone write FDone;
   property Info: String read FInfo write FInfo;
   property SEI: TShellExecuteInfo read FSEI write FSEI;
 end;

 TProcesses = class(TCollection)
  private
   function GetItem(Index: Integer): Process;
   procedure SetItem(Index: Integer; const Value: Process);
  public
   function Add: Process;
   function Insert(Index: Integer): Process;
   property Items[Index: Integer]: Process read GetItem write SetItem; default;
 end;

const
 myPluginName = 'Ultimate Sheduler';
 PluginTextDomain = 'UltimateSheduler';
 NoCancel = 'NOCANCEL';
 //
 ultDemand = 0;
 ultDemandTitle = 'On demand';
 ultTime = 1;
 ultTimeTitle = 'On time';
 ultEvent = 2;
 ultEventTitle = 'On event';
 //
 ultt_Once = 0;
 ultt_OnceTitle = 'Once';
 ultt_Everyday = 1;
 ultt_EverydayTitle = 'Every day';
 ultt_DaysOfWeek = 2;
 ultt_DaysOfWeekTitle = 'Days of week';
 ultt_DaysOfMonth = 3;
 ultt_DaysOfMonthTitle = 'Days of month';
 ultt_ByPeriod = 4;
 ultt_ByPeriodTitle = 'By period';
 //
 ulte_DMStart = 0;
 ulte_Connect = 1;
 ulte_AllDownloads = 2;
 ulte_DownloadAdded = 3;
 ulte_DownloadDeleted = 4;
 ulte_DownloadState = 5;
 ulte_Timer60 = 6;
 ulte_Timer10 = 7;
 ulte_Timer05 = 8;

 DaysOfWeek: array [0..6] of String = ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday');

 TitleSep = #9;

 cndIndex = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
 //condition types
 cndCategory = 1;
 cndCategoryDesc = 'Is from category';
 cndURL = 2;
 cndURLDesc = 'Has URL';
 cndFolder = 3;
 cndFolderDesc = 'Saved to folder';
 cndFilename = 4;
 cndFilenameDesc = 'Have filename';
 cndCategoryHas = 5;
 cndCategoryHasDesc = 'Category has downloads';
 cndSizeLT = 10;
 cndSizeLTDesc = 'Size is less than';
 cndSizeEQ = 11;
 cndSizeEQDesc = 'Size is equal to';
 cndSizeGT = 12;
 cndSizeGTDesc = 'Size is greater than';
 cndFlagExists = 100;
 cndFlagExistsDesc = 'Flag exists';

 //action types
 actCopy = 1;
 actCopyDesc = 'Copy file to';
 actMove = 2;
 actMoveDesc = 'Move file to';
 actLaunch = 3;
 actLaunchDesc = 'Launch program';
 actStart = 4;
 actStartDesc = 'Start download the category';
 actFlagCreate = 5;
 actFlagCreateDesc = 'Create flag';
 actFlagDelete = 6;
 actFlagDeleteDesc = 'Delete flag';
 actRename = 7;
 actRenameDesc = 'Rename to';

 actexRename = 0;
 actexAsk = 1;
 actexOverwrite = 2;

 ridDelete = -3;
 ridNotSaved = -2;
 ridNoRule = -1;

 aidComplete = -2;
 aidNoAction = -1;

 dNotStarted = -1;
 dDone = 0;
 dContinue = 1;

 //
 idComplete = -1;
 idFindFirst = -2;
 idFindNext = -3;
 idWaitForFile = -4;
 idWaitForProcess = -5;

 //
 AllFilesFilter = 'All files (*.*)|*.*';
 TaskExt = '.task';
 TasksFilter = 'Tasks (*'+TaskExt+')|*'+TaskExt;
 RuleExt = '.rule';
 RulesFilter = 'Rules (*'+RuleExt+')|*'+RuleExt;
 AGExt = '.agroup';
 ActionsFilter = 'Action groups (*'+AGExt+')|*'+AGExt;
 //
 sTask = 'task';
 sTaskC = 'Task';
 sRule = 'rule';
 sRuleC = 'Rule';
 sAG = 'action group';
 sAGC = 'Action group';
 //
 msgTitleNoEmpty = 'Title cannot be empty!';
 msgTitleExists = 'Title "%s" already exists! Please type another one.';

 logError = 'Error';
 logKindNotMatchData = 'Task "%s": data "%s" does not match to kind of task "%s".';
 logInvalidDay = 'Task "%s": invalid day "%s" for kind "%s".';
 logInvalidTime = 'Task "%s": invalid time "%s".';
 logInvalidPeriod = 'Task "%s": invalid period "%s".';
 logTask = 'Task';
 logTaskAdded = 'Task %s (Rule: %s; Actions Group: %s) was added to queue.';
 logTaskDisabled = 'Task "%s" is disabled.';
 logAction = 'Action';
 logActionGroupMissing = 'Action group "%s" is missing. Task aborted.';
 logFiles = 'File';
 logThread = 'Thread';

var
 mySettings: Settings = nil;
 PluginsPath: String = '';
 LogsPath: String = '';
 LogFilename: String = '';
 CatList: TStringList = nil;
 XMLFile: String = '';
 myFunction: TExtCondFunc = nil;
 XML: TXMLSerializer = nil;
 //
 CS_Tasks: TCriticalSection = nil;
 CS_Rules: TCriticalSection = nil;
 CS_Actions: TCriticalSection = nil;

function TrimQuotes(AStr: String): String;
function GetLaunchTypeTitle(const AType: Integer): String;
function GetLaunchKindTitle(const AType, AKind: Integer): String;
function DayOfWeekStr(DOW: Integer): String;
function GetCondTypeDesc(ACondType: Integer): String;
function GetAvailCondIndex(Used: String): Char;
function GetActTypeDesc(AActType: Integer): String;
function CheckTitle(AForm: TForm; const LE: TLabeledEdit; const TitlesList: String; OldTitle: String = ''): Boolean;
function CompareDateTimeSec(const A, B: TDateTime; WithSeconds: Boolean = false): TValueRelationship;
function IsProcessActive(ASEI: TShellExecuteInfo): Boolean;
function EventToET(const Event: String): Integer;

implementation

uses
 Windows,
 Dialogs,
 Math,
 DateUtils,
 formula,
 RegExpr,
 Tokens,
 StringUtils;

 { Task }

constructor Task.Create(Collection: TCollection);
begin
 inherited Create(Collection);
 FEnabled:=false;
 FTitle:='';
 FLaunchType:=0;
 FLaunchKind:=0;
 FLaunchData:='';
 FRuleTitle:='';
 FActionsTitle:='';
 FLastLaunch:=0;
 FNextLaunch:=0;
end;

procedure Task.Assign(Source: TPersistent);
begin
 if Assigned(Source) then
  begin
   if Source is Task then
    begin
     Self.Enabled:=Task(Source).Enabled;
     Self.Title:=Task(Source).Title;
     Self.LaunchType:=Task(Source).LaunchType;
     Self.LaunchKind:=Task(Source).LaunchKind;
     Self.LaunchData:=Task(Source).LaunchData;
     Self.RuleTitle:=Task(Source).RuleTitle;
     Self.ActionsTitle:=Task(Source).ActionsTitle;
     Self.LastLaunch:=Task(Source).LastLaunch;
     Self.NextLaunch:=Task(Source).NextLaunch;
    end
   else
     inherited Assign(Source);
  end;
end;

procedure Task.SetupNextLaunch;
 var
  i, t, k: Integer;
  s: String;
  b: Boolean;
  DT: TDateTime;
begin
 if LaunchType=ultTime then
  begin
   case LaunchKind of
    ultt_Once:
     begin
      if TryStrToDateTime(LaunchData, DT) then
        NextLaunch:=DT
      else
       begin
        Log(logError, Format(_(logKindNotMatchData), [Title, LaunchData, GetLaunchKindTitle(LaunchType, LaunchKind)]));
        Enabled:=false;
        Log(logError, Format(_(logTaskDisabled), [Title]));
       end;
     end;
    ultt_Everyday:
     begin
      if TryStrToTime(LaunchData, DT) then
       begin
        NextLaunch:=Today+DT;
        while CompareDateTime(LastLaunch, NextLaunch)>=0 do
          NextLaunch:=IncDay(NextLaunch);
       end
      else
       begin
        Log(logError, Format(_(logKindNotMatchData), [Title, LaunchData, GetLaunchKindTitle(LaunchType, LaunchKind)]));
        Enabled:=false;
        Log(logError, Format(_(logTaskDisabled), [Title]));
       end;
     end;
    ultt_DaysOfWeek, ultt_DaysOfMonth:
     begin
      k:=WordCount(LaunchData, [' ']);
      if k>1 then
       begin
        for i:=2 to k do
         begin
          s:=ExtractWord(i, LaunchData, [' ']);
          t:=StrToIntDef(s, 0);
          if LaunchKind=ultt_DaysOfMonth then
            b:=t in [1..31]
          else
            b:=t in [1..7];
          if not b then
           begin
            Log(logError, Format(_(logInvalidDay), [Title, s, GetLaunchKindTitle(LaunchType, LaunchKind)]));
            Enabled:=false;
            Log(logError, Format(_(logTaskDisabled), [Title]));
            Exit;
           end;
         end;
        s:=ExtractWord(1, LaunchData, [' ']);
        if TryStrToTime(s, DT) then
         begin
          NextLaunch:=Today+DT;
          repeat
           if LaunchKind=ultt_DaysOfMonth then
             b:=WordInString(IntToStr(DayOf(DT)), LaunchData)>0
           else
             b:=WordInString(IntToStr(DayOfTheWeek(DT)), LaunchData)>0;
           if not b then
             NextLaunch:=IncDay(NextLaunch);
          until b and (CompareDateTime(LastLaunch, NextLaunch)<0);
         end
        else
         begin
          Log(logError, Format(_(logInvalidTime), [Title, s]));
          Enabled:=false;
          Log(logError, Format(_(logTaskDisabled), [Title]));
         end;
       end
      else
       begin
        Log(logError, Format(_(logKindNotMatchData), [Title, LaunchData, GetLaunchKindTitle(LaunchType, LaunchKind)]));
        Enabled:=false;
        Log(logError, Format(_(logTaskDisabled), [Title]));
       end;
     end;
    ultt_ByPeriod:
     begin
      t:=StrToIntDef(LaunchData, 0);
      if t>0 then
       begin
        NextLaunch:=IncMinute(Now, t);
        while CompareDateTime(LastLaunch, NextLaunch)>=0 do
          NextLaunch:=IncMinute(NextLaunch, t);
       end
      else
       begin
        Log(logError, Format(_(logInvalidPeriod), [Title, LaunchData]));
        Enabled:=false;
        Log(logError, Format(_(logTaskDisabled), [Title]));
       end;
     end;
    end;
  end
 else
   NextLaunch:=0;
end;

 { USTasks }

function USTasks.Add: Task;
begin
 Result := Task(inherited Add);
end;

function USTasks.GetItem(Index: Integer): Task;
begin
 Result := Task(inherited Items[Index]);
end;

function USTasks.Insert(Index: Integer): Task;
begin
 Result := Task(inherited Insert(Index));
end;

procedure USTasks.SetItem(Index: Integer; const Value: Task);
begin
 Items[Index].Assign(Value);
end;

function USTasks.IndexOfTitle(const ATitle: String): Integer;
begin
 Result:=Self.Count-1;
 while Result>=0 do
  begin
   if SameText(Items[Result].Title, ATitle) then
     Break
   else
     Dec(Result);
  end;
end;

function USTasks.ListOfTitles: String;
 var
  i: Integer;
begin
 Result:='';
 for i:=0 to Self.Count-1 do
  begin
   if Result='' then
     Result:=Items[i].Title
   else
     Result:=Result+#13#10+Items[i].Title;
  end;
end;

procedure USTasks.SetupNextLaunchTime;
 var
  i: Integer;
begin
 for i:=0 to Count-1 do
   Items[i].SetupNextLaunch;
end;

function USTasks.CheckLaunchTime: String;
 var
  i: Integer;
begin
 Result:='';
 for i:=0 to Count-1 do
  begin
   if Items[i].FLaunchType=ultTime then
    begin
     if CompareDateTimeSec(Now, Items[i].NextLaunch)=0 then
      begin
       if Result='' then
         Result:=Items[i].Title
       else
         Result:=Result+#13#10+Items[i].Title;
      end;
    end;
  end;
end;

function USTasks.CheckLaunchEvent(const Event: String): String;
 var
  i: Integer;
  s: String;
begin
 Result:='';
 s:=ExtractWord(1, Event, [' ']);
 for i:=0 to Count-1 do
  begin
   if Items[i].LaunchType=ultEvent then
    begin
     if Items[i].LaunchKind=EventToET(s) then
      begin
       if Items[i].LaunchKind=ulte_DownloadState then
        begin
         s:=ExtractWord(3, Event, [' ']);
         if WordInString(s, Items[i].LaunchData)>0 then
          begin
           if Result='' then
             Result:=Items[i].Title+TitleSep+ExtractWord(2, Event, [' '])
           else
             Result:=Result+#13#10+Items[i].Title+TitleSep+ExtractWord(2, Event, [' ']);
          end;
        end
       else
        begin
         if Result='' then
           Result:=Items[i].Title
         else
           Result:=Result+#13#10+Items[i].Title;
         if Items[i].LaunchKind in [ulte_DownloadAdded, ulte_DownloadDeleted] then
           Result:=Result+TitleSep+ExtractWord(2, Event, [' ']);
        end;
      end;
    end;
  end;
end;

 { Condition }

constructor Condition.Create(Collection: TCollection);
begin
 inherited Create(Collection);
 CondIndex:=#0;
 CondType:=-1;
 Parameter:='';
 ExternalFlag:=false;
end;

procedure Condition.Assign(Source: TPersistent);
begin
 if Assigned(Source) then
  begin
   if Source is Condition then
    begin
     Self.CondIndex:=Condition(Source).CondIndex;
     Self.CondType:=Condition(Source).CondType;
     Self.Parameter:=Condition(Source).Parameter;
     Self.ExternalFlag:=Condition(Source).ExternalFlag;
    end
   else
     inherited Assign(Source);
  end;
end;

procedure Condition.Clear;
begin
 CondIndex:=#0;
 CondType:=0;
 Parameter:='';
end;

 { TConditions }

function TConditions.Add: Condition;
begin
 Result := Condition(inherited Add);
end;

function TConditions.GetItem(Index: Integer): Condition;
begin
 Result := Condition(inherited Items[Index]);
end;

function TConditions.Insert(Index: Integer): Condition;
begin
 Result := Condition(inherited Insert(Index));
end;

procedure TConditions.SetItem(Index: Integer; const Value: Condition);
begin
 Items[Index].Assign(Value);
end;

function TConditions.GetUsedIndexes: String;
 var
  i: Integer;
begin
 Result:='';
 for i:=0 to Count-1 do
   Result:=Result+Items[i].CondIndex;
end;

{ Rule }

constructor Rule.Create(Collection: TCollection);
begin
 inherited Create(Collection);
 Title:='';
 Formula:='';
 Conditions := TConditions.Create(Condition);
 ExtFunction := nil;
end;

destructor Rule.Destroy;
begin
 FreeAndNil(FConditions);
 ExtFunction:=nil;
 inherited Destroy;
end;

procedure Rule.Assign(Source: TPersistent);
begin
 if Assigned(Source) then
  begin
   if Source is Rule then
    begin
     Self.Title:=Rule(Source).Title;
     Self.Formula:=Rule(Source).Formula;
     Self.Conditions.Assign(Rule(Source).Conditions);
     Self.ExtFunction:=Rule(Source).ExtFunction;
    end
   else
     inherited Assign(Source);
  end;
end;

function Rule.Matched(const Info: String): Boolean;
 var
  AF: TArtFormula;
  re: TRegExpr;
  vars: array of String;
  vals: TCalcArray;
  i, num, valInfo, valCond: Integer;
  s, sCat: String;
  e: ERuleException;
begin
 Result:=false;
 //
 re:=TRegExpr.Create;
 re.ModifierI:=true;
 //
 AF:=TArtFormula.Create(nil);
 AF.TestUsedVars:=true;
 num:=Conditions.Count;
 SetLength(vars, num);
 SetLength(vals, num);
 //fill vals with conditions' values
 for i:=0 to num-1 do
  begin
   vars[i]:=Conditions[i].CondIndex;
   //external function call
   if Conditions[i].ExternalFlag then
    begin
     if Assigned(ExtFunction) then
       setN(vals[i], IfThen(ExtFunction(Conditions[i]), 1, 0))
     else
      begin
       e:=ERuleException.CreateFmt(_('External function is not assigned in rule %s.'), [Self.Title]);
       raise e;
      end;
     Continue;
    end;
   //general function call
   case Conditions[i].CondType of
    cndCategory:
     begin
      s:=ExtractToken('categoryid', Info);
      sCat:=CatList.Values[s];
      if sCat='' then
        setN(vals[i], 0) //no category with specified index
      else
        setN(vals[i], IfThen(sCat=Conditions[i].Parameter, 1, 0));
     end;
    cndURL:
     begin
      s:=ExtractToken('url', Info);
      re.Expression:=Conditions[i].Parameter;
      try
       setN(vals[i], IfThen(re.Exec(s), 1, 0));
      except
       on E:Exception do
        begin
         setN(vals[i], 0);
        end;
      end;
     end;
    cndFolder:
     begin
      s:=ExtractFilePath(ExtractToken('saveto', Info));
      setN(vals[i], IfThen(SameText(IncludeTrailingPathDelimiter(s), IncludeTrailingPathDelimiter(Conditions[i].Parameter)), 1, 0));
     end;
    cndFilename:
     begin
      s:=ExtractFileName(ExtractToken('saveto', Info));
      re.Expression:=Conditions[i].Parameter;
      try
       setN(vals[i], IfThen(re.Exec(s), 1, 0));
      except
       on E:Exception do
        begin
         setN(vals[i], 0);
        end;
      end;
     end;
    cndSizeLT, cndSizeEQ, cndSizeGT:
     begin
      s:=ExtractToken('size', Info);
      valInfo:=StrToIntDef(s, -1);
      if valInfo<0 then
        setN(vals[i], 0) //error in info
      else
       begin
        valCond:=StrToIntDef(Conditions[i].Parameter, -1);
        if valCond<0 then
         begin
          setN(vals[i], 0);
         end
        else
         begin
          case Conditions[i].CondType of
           cndSizeLT: setN(vals[i], IfThen(valInfo<valCond, 1, 0));
           cndSizeEQ: setN(vals[i], IfThen(valInfo=valCond, 1, 0));
           cndSizeGT: setN(vals[i], IfThen(valInfo>valCond, 1, 0));
           end;
         end;
       end;
     end;
    cndFlagExists:
     begin
      setN(vals[i], IfThen(FileExists(Conditions[i].Parameter), 1, 0));
     end;
    else
      setN(vals[i], 0);
    end;
  end;
 try
  Result:=StrToBoolDef(AF.ComputeStr(Self.Formula, num, @vars, @vals), false);
 except
  on E:Exception do
   begin
    Result:=false;
   end;
 end;
 FreeAndNil(AF);
 FreeAndNil(re);
end;

{ TRules }

function TRules.Add(AFunction: TExtCondFunc): Rule;
begin
 Result := Rule(inherited Add);
 Result.ExtFunction:=AFunction;
end;

function TRules.GetItem(Index: Integer): Rule;
begin
 Result := Rule(inherited Items[Index]);
end;

function TRules.Insert(Index: Integer): Rule;
begin
 Result := Rule(inherited Insert(Index));
end;

procedure TRules.SetItem(Index: Integer; const Value: Rule);
begin
 Items[Index].Assign(Value);
end;

function TRules.IndexOfTitle(const ATitle: String): Integer;
begin
 Result:=Self.Count-1;
 while Result>=0 do
  begin
   if SameText(Items[Result].Title, ATitle) then
     Break
   else
     Dec(Result);
  end;
end;

function TRules.ListOfTitles: String;
 var
  i: Integer;
begin
 Result:='';
 for i:=0 to Self.Count-1 do
  begin
   if Result='' then
     Result:=Items[i].Title
   else
     Result:=Result+#13#10+Items[i].Title;
  end;
end;

 { anAction }

procedure anAction.Assign(Source: TPersistent);
begin
 if Assigned(Source) then
  begin
   if Source.ClassNameIs(Self.ClassName) then
    begin
     Self.Enabled:=(Source as anAction).Enabled;
     Self.ActType:=(Source as anAction).ActType;
     Self.Destination:=(Source as anAction).Destination;
     Self.Parameter:=(Source as anAction).Parameter;
     Self.Flag:=(Source as anAction).Flag;
     Self.Existing:=(Source as anAction).Existing;
    end
   else
     inherited Assign(Source);
  end;
end;

procedure anAction.Clear;
begin
 Enabled:=false;
 ActType:=0;
 Destination:='';
 Parameter:='';
 Flag:=false;
 Existing:=0;
end;

 { TActions }

function TActions.Add: anAction;
begin
 Result := anAction(inherited Add);
end;

function TActions.GetItem(Index: Integer): anAction;
begin
 Result := anAction(inherited Items[Index]);
end;

function TActions.Insert(Index: Integer): anAction;
begin
 Result := anAction(inherited Insert(Index));
end;

procedure TActions.SetItem(Index: Integer; const Value: anAction);
begin
 Items[Index].Assign(Value);
end;

{ ActionGroup }

constructor ActionGroup.Create(Collection: TCollection);
begin
 inherited Create(Collection);
 Title:='';
 Actions := TActions.Create(anAction);
end;

destructor ActionGroup.Destroy;
begin
 FreeAndNil(FActions);
 inherited Destroy;
end;

procedure ActionGroup.Assign(Source: TPersistent);
begin
 if Assigned(Source) then
  begin
   if Source is ActionGroup then
    begin
     Self.Title:=ActionGroup(Source).Title;
     Self.Actions.Assign(ActionGroup(Source).Actions);
    end
   else
     inherited Assign(Source);
  end;
end;

 { TActionGroups }

function TActionGroups.Add: ActionGroup;
begin
 Result := ActionGroup(inherited Add);
end;

function TActionGroups.GetItem(Index: Integer): ActionGroup;
begin
 Result := ActionGroup(inherited Items[Index]);
end;

function TActionGroups.Insert(Index: Integer): ActionGroup;
begin
 Result := ActionGroup(inherited Insert(Index));
end;

procedure TActionGroups.SetItem(Index: Integer; const Value: ActionGroup);
begin
 Items[Index].Assign(Value);
end;

function TActionGroups.IndexOfTitle(const ATitle: String): Integer;
begin
 Result:=Self.Count-1;
 while Result>=0 do
  begin
   if SameText(Items[Result].Title, ATitle) then
     Break
   else
     Dec(Result);
  end;
end;

function TActionGroups.ListOfTitles: String;
 var
  i: Integer;
begin
 Result:='';
 for i:=0 to Self.Count-1 do
  begin
   if Result='' then
     Result:=Items[i].Title
   else
     Result:=Result+#13#10+Items[i].Title;
  end;
end;

{ Settings }

constructor Settings.Create;
begin
 inherited Create;
 FTasks:=USTasks.Create(Task);
 FRules:=TRules.Create(Rule);
 FActionGroups:=TActionGroups.Create(ActionGroup);
end;

destructor Settings.Destroy;
begin
 FreeAndNil(FActionGroups);
 FreeAndNil(FRules);
 FreeAndNil(FTasks);
 inherited;
end;

procedure Settings.Assign(Source: TPersistent);
begin
 if Assigned(Source) then
  begin
   if Source is Settings then
    begin
     Self.LogEnabled:=Settings(Source).LogEnabled;
     Self.LogDM:=Settings(Source).LogDM;
     Self.LogSave:=Settings(Source).LogSave;
     Self.LogSize:=Settings(Source).LogSize;
     Self.Silent:=Settings(Source).Silent;
     Self.Tasks.Assign(Settings(Source).Tasks);
     Self.Rules.Assign(Settings(Source).Rules);
     Self.ActionGroups.Assign(Settings(Source).ActionGroups);
    end
   else
     inherited Assign(Source);
  end;
end;

{ SingleTask }

constructor SingleTask.Create;
begin
 inherited Create;
 FTask:=Task.Create(nil);
end;

destructor SingleTask.Destroy;
begin
 FreeAndNil(FTask);
 inherited;
end;

procedure SingleTask.Assign(Source: TPersistent);
begin
 if Assigned(Source) then
  begin
   if Source is SingleTask then
    begin
     Self.aTask.Assign(SingleTask(Source).aTask);
    end
   else
     inherited Assign(Source);
  end;
end;

{ SingleRule }

constructor SingleRule.Create;
begin
 inherited Create;
 FRule:=Rule.Create(nil);
end;

destructor SingleRule.Destroy;
begin
 FreeAndNil(FRule);
 inherited;
end;

procedure SingleRule.Assign(Source: TPersistent);
begin
 if Assigned(Source) then
  begin
   if Source is SingleRule then
    begin
     Self.aRule.Assign(SingleRule(Source).aRule);
    end
   else
     inherited Assign(Source);
  end;
end;

{ SingleActionGroup }

constructor SingleActionGroup.Create;
begin
 inherited Create;
 FActionGroup:=ActionGroup.Create(nil);
end;

destructor SingleActionGroup.Destroy;
begin
 FreeAndNil(FActionGroup);
 inherited;
end;

procedure SingleActionGroup.Assign(Source: TPersistent);
begin
 if Assigned(Source) then
  begin
   if Source is SingleActionGroup then
    begin
     Self.anActionGroup.Assign(SingleActionGroup(Source).anActionGroup);
    end
   else
     inherited Assign(Source);
  end;
end;

 { Process }

constructor Process.Create(Collection: TCollection);
begin
 inherited Create(Collection);
 FTaskID:=idComplete; //no task id defined
 FRuleID:=idComplete; //no rule id defined; rule matched when checked
 FActionGroupID:=idFindFirst; //no AG id defined yet
 FActionID:=idFindFirst; //no action id defined yet
 //FDone:=dNotStarted;
 FInfo:='';
 SEI_Clear;
end;

procedure Process.Assign(Source: TPersistent);
begin
 if Assigned(Source) then
  begin
   if Source is Process then
    begin
     Self.TaskID:=Process(Source).TaskID;
     Self.RuleID:=Process(Source).RuleID;
     Self.ActionGroupID:=Process(Source).ActionGroupID;
     Self.ActionID:=Process(Source).ActionID;
     //Self.Done:=Process(Source).Done;
     Self.Info:=Process(Source).Info;
     Self.SEI:=Process(Source).SEI;
    end
   else
     inherited Assign(Source);
  end;
end;

procedure Process.SEI_Clear;
begin
 FillChar(FSEI, SizeOf(TShellExecuteInfo), 0);
end;

{ TProcesses }

function TProcesses.Add: Process;
begin
 Result := Process(inherited Add);
end;

function TProcesses.GetItem(Index: Integer): Process;
begin
 Result := Process(inherited Items[Index]);
end;

function TProcesses.Insert(Index: Integer): Process;
begin
 Result := Process(inherited Insert(Index));
end;

procedure TProcesses.SetItem(Index: Integer; const Value: Process);
begin
 Items[Index].Assign(Value);
end;

 { Common stuff }

function GetLaunchTypeTitle(const AType: Integer): String;
begin
 case AType of
  ultDemand: Result:=_(ultDemandTitle);
  ultTime: Result:=_(ultTimeTitle);
  ultEvent: Result:=_(ultEventTitle);
  else Result:=_('Unknown launch type');
  end;
end;

function GetLaunchKindTitle(const AType, AKind: Integer): String;
begin
 Result:='';
 case AType of
  ultTime:
   begin
    case AKind of
     ultt_Once: Result:=_(ultt_OnceTitle);
     ultt_Everyday: Result:=_(ultt_EverydayTitle);
     ultt_DaysOfWeek: Result:=_(ultt_DaysOfWeekTitle);
     ultt_DaysOfMonth: Result:=_(ultt_DaysOfMonthTitle);
     ultt_ByPeriod: Result:=_(ultt_ByPeriodTitle);
     end;
   end;
  ultEvent:
   begin
   end;
  end;
end;

function DayOfWeekStr(DOW: Integer): String;
begin
 Result:=_(DaysOfWeek[DOW mod 7]);
end;

function TrimQuotes(AStr: String): String;
begin
 Result:=AStr;
 if Length(Result)>0 then
  begin
   if Result[1]='"' then
     Delete(Result, 1, 1);
   if Length(Result)>0 then
     if Result[Length(Result)]='"' then
       Delete(Result, Length(Result), 1);
  end;
end;

function GetCondTypeDesc(ACondType: Integer): String;
begin
 case ACondType of
  cndCategory: Result:=_(cndCategoryDesc);
  cndCategoryHas: Result:=_(cndCategoryHasDesc);
  cndURL: Result:=_(cndURLDesc);
  cndFolder: Result:=_(cndFolderDesc);
  cndFilename: Result:=_(cndFilenameDesc);
  cndSizeLT: Result:=_(cndSizeLTDesc);
  cndSizeEQ: Result:=_(cndSizeEQDesc);
  cndSizeGT: Result:=_(cndSizeGTDesc);
  cndFlagExists: Result:=_(cndFlagExistsDesc);
  else
    Result:=_('Unknown type');
  end;
end;

function GetAvailCondIndex(Used: String): Char;
 var
  i: Integer;
begin
 Result:=#0;
 i:=1;
 repeat
  if Pos(cndIndex[i], Used)=0 then
    Result:=cndIndex[i];
  Inc(i);
 until (Result<>#0) or (i>=Length(cndIndex));
end;

function GetActTypeDesc(AActType: Integer): String;
begin
 case AActType of
  actCopy: Result:=_(actCopyDesc);
  actMove: Result:=_(actMoveDesc);
  actLaunch: Result:=_(actLaunchDesc);
  actStart: Result:=_(actStartDesc);
  actFlagCreate: Result:=_(actFlagCreateDesc);
  actFlagDelete: Result:=_(actFlagDeleteDesc);
  actRename: Result:=_(actRenameDesc);
  else
    Result:=_('Unknown type');
  end;
end;

function CheckTitle(AForm: TForm; const LE: TLabeledEdit; const TitlesList: String; OldTitle: String = ''): Boolean;
begin
 if Assigned(AForm) and Assigned(LE) then
  begin
   Result:=true;
   if LE.Text='' then
    begin
     MessageDlg(_(msgTitleNoEmpty), mtError, [mbOK], 0);
     AForm.ActiveControl:=LE;
     Result:=false;
     Exit;
    end;
   //
   if (CompareStr(LE.Text, OldTitle)<>0) and (WordInString(LE.Text, TitlesList, [#13, #10])>0) then
    begin
     AForm.ActiveControl:=LE;
     MessageDlg(Format(_(msgTitleExists), [LE.Text]), mtError, [mbOK], 0);
     Result:=false;
     Exit;
    end;
  end
 else
   Result:=false;
end;

function CompareDateTimeSec(const A, B: TDateTime; WithSeconds: Boolean = false): TValueRelationship;
 var
  AYear, AMonth, ADay, AHour, AMinute, ASecond, AMilliSecond,
  BYear, BMonth, BDay, BHour, BMinute, BSecond, BMilliSecond: Word;
  AA, BB: TDateTime;
begin
 DecodeDateTime(A, AYear, AMonth, ADay, AHour, AMinute, ASecond, AMilliSecond);
 DecodeDateTime(B, BYear, BMonth, BDay, BHour, BMinute, BSecond, BMilliSecond);
 if not WithSeconds then
  begin
   ASecond:=0;
   BSecond:=0;
  end;
 AA:=EncodeDateTime(AYear, AMonth, ADay, AHour, AMinute, ASecond, 0);
 BB:=EncodeDateTime(BYear, BMonth, BDay, BHour, BMinute, BSecond, 0);
 Result:=CompareDateTime(AA, BB);
end;

function IsProcessActive(ASEI: TShellExecuteInfo): Boolean;
 var
  exitcode: Cardinal;
begin
 if ASEI.hProcess=0 then
   Result:=false
 else
   Result:=GetExitCodeProcess(ASEI.hProcess, exitcode) and (exitcode=STILL_ACTIVE);
end;

function EventToET(const Event: String): Integer;
begin
 Result:=-1;
 if SameText('dm_timer_5', Event) then
   Result:=ulte_Timer05;
 if SameText('dm_timer_10', Event) then
   Result:=ulte_Timer10;
 if SameText('dm_timer_60', Event) then
   Result:=ulte_Timer60;
 if SameText('dm_start', Event) then
   Result:=ulte_DMStart;
 if SameText('dm_connect', Event) then
   Result:=ulte_Connect;
 if SameText('dm_downloadall', Event) then
   Result:=ulte_AllDownloads;
 if SameText('dm_download_added', Event) then
   Result:=ulte_DownloadAdded;
 if SameText('dm_download_deleted', Event) then
   Result:=ulte_DownloadDeleted;
 if SameText('dm_download_state', Event) then
   Result:=ulte_DownloadState;
end;

end.
